package com.cyw.common.annotations.support;

import com.cyw.common.annotations.AccessRateLimit;
import com.cyw.common.config.redis.RedisManager;
import com.cyw.common.domain.common.Response;
import com.cyw.common.exceptions.BaseException;
import com.cyw.common.utils.IPUtils;
import com.cyw.common.utils.JsonUtils;
import com.cyw.common.utils.ResponseUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.Objects;

import static com.cyw.common.utils.BaseUtils.isNotEmpty;

/**
 * @Author:cyw
 * @CreateTime: 2021/3/11 13:47
 **/
@Aspect
@Component
public class AccessRateLimitAspect {

    @Resource
    private RedisManager<String> redisManager;
    private final static String RATE_LIMIT_COUNTER_PREFIX = "rateLimit::counter::";
    private final static String RATE_LIMIT_BLACK_LIST_PREFIX = "rateLimit::blackList::";

    @Pointcut(" @annotation(com.cyw.common.annotations.AccessRateLimit)")
    public void rateLimit() {
    }

    @Around("rateLimit()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {

        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method targetMethod = signature.getMethod();
        AccessRateLimit limit = targetMethod.getAnnotation(AccessRateLimit.class);

        String key =
                isNotEmpty(limit.key()) ? limit.key() :
                        targetMethod.getDeclaringClass().getName() + "::" + targetMethod.getName() + "::";

        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        Objects.requireNonNull(requestAttributes);
        ServletRequestAttributes attributes = (ServletRequestAttributes) requestAttributes;
        HttpServletRequest request = attributes.getRequest();
        HttpServletResponse response = attributes.getResponse();

        String ip = IPUtils.getIp(request);
        String redisCounterKey = RATE_LIMIT_COUNTER_PREFIX + key + ip;
        String redisBlackListKey = RATE_LIMIT_BLACK_LIST_PREFIX + key + ip;

        if (unboxing(redisManager.hasKey(redisBlackListKey))) {
            ResponseUtils.responseJson(response, JsonUtils.toJson(Response.fail(limit.errorMessage())));
            return null;
        }
        Long increment = redisManager.incr(redisCounterKey, 1L);
        if (unboxing(increment) == 1L) {
            redisManager.expire(redisCounterKey, 1, limit.type());
        }
        if (unboxing(increment) > limit.value()) {
            redisManager.set(redisBlackListKey, "", 1, limit.type());
        }
        return joinPoint.proceed();

    }

    private boolean unboxing(Boolean b) {
        if (b == null) {
            throw new BaseException("redis返回值错误");
        }
        return b;
    }

    private long unboxing(Long l) {
        if (l == null) {
            throw new BaseException("redis返回值错误");
        }
        return l;
    }
}